/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.archivers.tar;

import com.softwarecraftsmen.archivers.Archiver;
import static com.softwarecraftsmen.archivers.tar.BlockWriter.DefaultRecordsPerBlock;
import com.softwarecraftsmen.archivers.tar.fileEntries.DirectoryFileEntry;
import com.softwarecraftsmen.archivers.tar.fileEntries.FileEntry;
import com.softwarecraftsmen.archivers.tar.fileEntries.ReadableDataBackedFileEntry;
import com.softwarecraftsmen.orogeny.filing.AbsoluteDirectories;
import com.softwarecraftsmen.orogeny.filing.AbsoluteDirectory;
import com.softwarecraftsmen.orogeny.filing.AbsoluteFile;
import com.softwarecraftsmen.orogeny.filing.FileName;
import com.softwarecraftsmen.orogeny.filing.RelativeDirectory;
import com.softwarecraftsmen.orogeny.filing.RelativeFile;
import com.softwarecraftsmen.orogeny.filing.findFileFilters.AbstractFindFilesFilter;
import com.softwarecraftsmen.orogeny.filing.findFileFilters.FindFilesFilter;
import com.softwarecraftsmen.orogeny.filing.findSubdirectoriesFilters.AbstractFindSubdirectoriesFilter;
import com.softwarecraftsmen.outputStreamWriters.OutputStreamWriter;
import org.jetbrains.annotations.NotNull;

import java.io.BufferedOutputStream;
import java.io.IOException;
import static java.lang.String.format;
import java.util.Iterator;
import java.util.LinkedHashSet;
import static java.util.Locale.UK;
import java.util.Set;

public class TarArchiver implements Archiver
{
	@NotNull
	public static final TarArchiver TarArchive = new TarArchiver(false);

	@NotNull
	public static final TarArchiver AbsolutelyPathedTar = new TarArchiver(true);

	private final boolean makeAbsolute;

	public TarArchiver(final boolean makeAbsolute)
	{
		this.makeAbsolute = makeAbsolute;
	}

	public void archive(final @NotNull AbsoluteFile archiveFile, final @NotNull AbsoluteDirectories archiveRoots, final @NotNull FindFilesFilter includeFiles) throws IOException
	{
		final Set<RelativeDirectory> alreadySeenDirectories = new LinkedHashSet<>();
		final Set<RelativeFile> alreadySeenFiles = new LinkedHashSet<>();

		final LinkedHashSet<FileEntry> archiveEntries = new LinkedHashSet<FileEntry>()
		{{
			for (AbsoluteDirectory archiveRoot : archiveRoots)
			{
				archiveRoot.findSubdirectories(new AbstractFindSubdirectoriesFilter()
				{
					public boolean include(final @NotNull AbsoluteDirectory root, final @NotNull RelativeDirectory directoryInTar)
					{
						if (alreadySeenDirectories.contains(directoryInTar))
						{
							return false;
						}
						if (directoryInTar.isEmpty())
						{
							return false;
						}
						add(new DirectoryFileEntry(directoryInTar, makeAbsolute, directoryInTar.makeAbsolute(root).dynamicMetaData().fix()));
						alreadySeenDirectories.add(directoryInTar);
						return false;
					}
				});
				archiveRoot.findFiles(new AbstractFindFilesFilter()
				{
					public boolean include(final @NotNull AbsoluteDirectory root, final @NotNull RelativeDirectory relativeDirectory, final @NotNull FileName fileName)
					{
						if (!includeFiles.include(root, relativeDirectory, fileName))
						{
							return false;
						}
						final RelativeFile fileInArchive = new RelativeFile(relativeDirectory, fileName);
						if (alreadySeenFiles.contains(fileInArchive))
						{
							return false;
						}
						final AbsoluteFile data = fileInArchive.makeAbsolute(root);
						add(new ReadableDataBackedFileEntry(data, fileInArchive, makeAbsolute, data.dynamicMetaData().fix()));
						alreadySeenFiles.add(fileInArchive);
						return false;
					}
				});
			}
		}};
		archive(archiveFile, archiveEntries);
	}

	public void archive(final @NotNull AbsoluteFile archiveFile, final @NotNull Set<FileEntry> archiveEntries) throws IOException
	{
		archive(archiveFile, archiveEntries.iterator());
	}

	public void archive(final @NotNull AbsoluteFile archiveFile, final @NotNull Iterator<FileEntry> archiveEntries) throws IOException
	{
		archiveFile.writeData(new OutputStreamWriter()
		{
			public void write(final int outputBufferSize, final @NotNull BufferedOutputStream outputStream) throws IOException
			{
				final TarCreator tarCreator = new TarCreator(outputStream, DefaultRecordsPerBlock);
				while(archiveEntries.hasNext())
				{
					tarCreator.writeFileEntry(archiveEntries.next());
				}
				tarCreator.finish();
			}
		});
	}

	@NotNull
	public String toString()
	{
		return format(UK, "TarArchive(%1$s)", makeAbsolute ? "Absolutely Pathed" : "Relatively Pathed");
	}
}
